﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace SharpSoft.WPF.Controls
{
    /// <summary>
    /// 力学容器
    /// </summary>
    public class MechanicsCanvas : Canvas
    {

        public MechanicsCanvas()
        {
            this._timeTracker = new TimeTracker();
            this._velocities = new Dictionary<UIElement, Vector>();

            this.Loaded += MechanicsCanvas_Loaded;
            this.Unloaded += MechanicsCanvas_Unloaded;
        }

        private void MechanicsCanvas_Unloaded(object sender, RoutedEventArgs e)
        {
            if (!DesignerProperties.GetIsInDesignMode(this))
            {
                CompositionTarget.Rendering -= CompositionTarget_Rendering;
            }
        }

        private void MechanicsCanvas_Loaded(object sender, RoutedEventArgs e)
        {
            if (!DesignerProperties.GetIsInDesignMode(this))
            {
                CompositionTarget.Rendering += CompositionTarget_Rendering;
            }
        }

        #region 成员
        private readonly TimeTracker _timeTracker;
        private TimeSpan lastUpdateTime;//记录上次更新的时间
        /// <summary>
        /// 储存元素的速度
        /// </summary>
        private readonly Dictionary<UIElement, Vector> _velocities;

        private readonly object asyncState = new object();
        #endregion

        #region 附加属性
        /// <summary>
        /// 获取元素的初始位置
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static Point GetOriginalPosition(DependencyObject obj)
        {
            return (Point)obj.GetValue(OriginalPositionProperty);
        }
        /// <summary>
        /// 设置元素的初始位置
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="value"></param>
        public static void SetOriginalPosition(DependencyObject obj, Point value)
        {
            obj.SetValue(OriginalPositionProperty, value);
        }
        /// <summary>
        /// 附加属性，用于记录子UI的原始位置
        /// </summary>
        public static readonly DependencyProperty OriginalPositionProperty =
            DependencyProperty.RegisterAttached("OriginalPosition", typeof(Point), typeof(MechanicsCanvas), new PropertyMetadata(new Point(double.NaN, double.NaN), new PropertyChangedCallback(OnOriginalPositionChanged)));

        private static void OnOriginalPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UIElement ele = (UIElement)d;
            var x = Canvas.GetLeft(ele);
            var y = Canvas.GetTop(ele);
            var p = (Point)e.NewValue;
            if (double.IsNaN(x))
            {
                Canvas.SetLeft(ele, p.X);
            }
            if (double.IsNaN(y))
            {
                Canvas.SetTop(ele, p.Y);
            }
        }

        /// <summary>
        /// 获取元素的目标位置
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static Point GetTargetPosition(DependencyObject obj)
        {
            return (Point)obj.GetValue(TargetPositionProperty);
        }
        /// <summary>
        /// 设置元素的目标位置
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="value"></param>
        public static void SetTargetPosition(DependencyObject obj, Point value)
        {
            obj.SetValue(TargetPositionProperty, value);
        }
        /// <summary>
        /// 附加属性，定义元素的目标位置
        /// </summary>
        public static readonly DependencyProperty TargetPositionProperty =
            DependencyProperty.RegisterAttached("TargetPosition", typeof(Point), typeof(MechanicsCanvas), new PropertyMetadata(default(Point)));



        #endregion

        #region 属性

        /// <summary>
        /// 边框的斥力（小于0 表示为吸引力）
        /// </summary>
        public double BorderForce { get; set; } = 0;

        /// <summary>
        /// 元素间的斥力（小于0 表示为吸引力）
        /// </summary>
        public double ChildForce { get; set; } = 0;

        /// <summary>
        /// 回位力（当元素位移后，该力促使元素始终向其初始位置移动）
        /// </summary>
        public double ReturnForce { get; set; } = 0;
        /// <summary>
        /// 目标力（该力促使元素始终向其目标点运动）
        /// </summary>
        public double TargetForce { get; set; } = 1;

        /// <summary>
        /// 阻力 （直接作用在每个元素上，每次更新位置时与当前速度相乘，为0时表示速度不变,越大表示阻力越大，为负数时表示加速）
        /// </summary>
        public double DragForce { get; set; } = 0.1d;
        /// <summary>
        /// 最小更新间隔(默认为每秒最多30帧)
        /// </summary>
        public TimeSpan MinUpdateInterval { get; set; } = TimeSpan.FromMilliseconds(1000 / 30);

        public TimeSpan ElapsedTime { get => _timeTracker.ElapsedTime; }
        /// <summary>
        /// 是否所有元素都处于静止状态
        /// </summary>
        public bool IsMotionless { get; set; }

        public bool IsRunning
        {
            get { return (bool)GetValue(IsRunningProperty); }
            set { SetValue(IsRunningProperty, value); }
        }

        public static readonly DependencyProperty IsRunningProperty =
            DependencyProperty.Register("IsRunning", typeof(bool), typeof(MechanicsCanvas), new PropertyMetadata(default(bool)));


        #endregion

        #region 方法
        /// <summary>
        /// 重置计时器
        /// </summary>
        public void ResetTime()
        {
            lastUpdateTime = TimeSpan.Zero;
            _timeTracker.Reset();
        }

        /// <summary>
        /// 强行设置元素的即时速度
        /// </summary>
        /// <param name="element"></param>
        /// <param name="velocity"></param>
        public void SetVelocity(UIElement element, Vector velocity)
        {
            lock (asyncState)
            {
                this._velocities[element] = velocity;
            }

        }
        /// <summary>
        /// 移除子项
        /// </summary>
        /// <param name="element"></param>
        public void RemoveChild(UIElement element)
        {
            this.Children.Remove(element);
        }
        #endregion

        #region 实现
        /// <summary>
        /// 在所有的子元素由运动变为静止时触发
        /// </summary>

        public event RoutedEventHandler BeenMotionless
        {
            add { AddHandler(BeenMotionlessEvent, value); }
            remove { RemoveHandler(BeenMotionlessEvent, value); }
        }

        public static readonly RoutedEvent BeenMotionlessEvent = EventManager.RegisterRoutedEvent(
        "BeenMotionless", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MechanicsCanvas));

        protected virtual void OnBeenMotionless()
        {
            RoutedEventArgs args = new RoutedEventArgs();
            args.RoutedEvent = MechanicsCanvas.BeenMotionlessEvent;
            this.RaiseEvent(args);
        }


        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (!IsRunning)
            {
                return;
            }
            this._timeTracker.Update();
            var etime = this._timeTracker.ElapsedTime;
            if (etime - lastUpdateTime >= MinUpdateInterval)
            {
                UpdateChildren((etime - lastUpdateTime).TotalSeconds);
                lastUpdateTime = etime;
                this.OnChildrenUpdated();
            }

        }
        /// <summary>
        /// 更新子元素的位置
        /// </summary>
        protected virtual void UpdateChildren(double seconds)
        {
            var c = LogicalTreeHelper.GetChildren(this);
            List<UIElement> children = new List<UIElement>();
            foreach (UIElement item in c)
            {
                children.Add(item);
            }
            Dictionary<UIElement, Rect> childrenbounds = new Dictionary<UIElement, Rect>();
            Dictionary<UIElement, Point> initPoses = new Dictionary<UIElement, Point>();
            Dictionary<UIElement, Point> targetPoses = new Dictionary<UIElement, Point>();
            foreach (UIElement child in children)
            {
                var key = child;
                var childtruePosition = GetPosition(child);
                var childRect = new Rect(childtruePosition, child.RenderSize);
                childrenbounds.Add(key, childRect);
                var initPos = GetOriginalPosition(child);//初始位置
                initPoses.Add(key, initPos);
                var targetPos = GetTargetPosition(child);//目标位置
                targetPoses.Add(key, targetPos);
            }
            bool ismove = false;
            System.Threading.Tasks.Parallel.ForEach(children, (KEY) =>
           {
               var child = (UIElement)KEY;
               var velocity = _velocities.ContainsKey(child) ? _velocities[child] : new Vector(0, 0);//获取当前速度 
               velocity = this.ProcessElementVelocity(childrenbounds, child, initPoses[child], targetPoses[child], velocity, seconds);//处理元素的速度
               if (velocity.Length > 0)
               {
                   ismove = true;
               }
               if (double.IsNaN(velocity.X))
               {
                   velocity.X = 0;
               }
               if (double.IsNaN(velocity.Y))
               {
                   velocity.Y = 0;
               }
               SetVelocity(child, velocity);
           });

            foreach (UIElement child in children)
            {
                var pos = GetPosition(child);//获取当前位置
                SetPosition(child, pos + (seconds * _velocities[child]));
            }
            if (!ismove)
            {//已经全部静止
                if (!IsMotionless)
                {
                    this.IsMotionless = true;
                    this.OnBeenMotionless();
                }
            }
            else
            {//还是运动状态
                if (this.IsMotionless)
                {
                    this.IsMotionless = false;
                }

            }
        }
        /// <summary>
        /// 处理元素的运动速度(这个运算过程为异步，如非必要，不要直接对UI进行操作)
        /// </summary>
        /// <param name="childrenBounds">所有元素的外框矩形</param>
        /// <param name="child">元素实例的hash</param>
        /// <param name="initPos">元素的初始位置</param>
        /// <param name="targetPos">元素的目标位置</param>
        /// <param name="curVelocity">当前速度</param>
        /// <param name="seconds">与上一帧的间隔时间，单位：秒</param>
        /// <returns>返回通过各种力运算后的新速度</returns>
        protected virtual Vector ProcessElementVelocity(Dictionary<UIElement, Rect> childrenBounds,
            UIElement child, Point initPos, Point targetPos, Vector curVelocity, double seconds)
        {
            if (curVelocity.Length < 2d)
            {
                curVelocity = new Vector(0, 0);
            }
            var childRect = childrenBounds[child];// new Rect(curPos, child.RenderSize);
            var childPos = new Point(childRect.X, childRect.Y);
            //计算作用到元素上的力
            var forces = new Vector(0, 0);

            if (BorderForce != 0d)
            {//边界的排斥力 
                forces.X += BorderForce / Math.Max(1, childRect.Left);
                forces.X -= BorderForce / Math.Max(1, RenderSize.Width - childRect.Right);
                forces.Y += BorderForce / Math.Max(1, childRect.Top);
                forces.Y -= BorderForce / Math.Max(1, RenderSize.Height - childRect.Bottom);
            }

            if (ChildForce != 0d)
            { //计算其他子元素对当前元素的排斥力
                foreach (var otherchild in childrenBounds)
                {
                    //dont push against itself
                    if (otherchild.Key == child)
                        continue;//排除自身

                    var otherchildRect = otherchild.Value;

                    //make sure rects aren't the same
                    if (otherchildRect == childRect)
                        continue;

                    //忽略尺寸为0的子元素
                    if (otherchildRect.Width == 0 && otherchildRect.Height == 0 ||
                        childRect.Width == 0 && childRect.Height == 0)
                        continue;

                    //vector from current other child to current child
                    //are they overlapping?  if so, distance is 0
                    var toChild = AreRectsOverlapping(childRect, otherchildRect)
                        ? new Vector(0, 0)
                        : VectorBetweenRects(childRect, otherchildRect);

                    var length = toChild.Length;
                    if (length < 1)
                    {//过于接近的元素使用它们矩形的中心计算推力
                        length = 1;
                        var childCenter = GetCenter(childRect);
                        var otherchildCenter = GetCenter(otherchildRect);
                        //compute toChild from the center of both rects
                        toChild = childCenter - otherchildCenter;
                    }

                    toChild.Normalize();
                    var childpush = ChildForce / length;//元素间的推力，距离越远推力越小

                    forces += toChild * childpush;
                }
            }

            if (ReturnForce != 0d)
            {//计算回位力 
                var toOriginal = initPos - childPos;

                var pospush = ReturnForce;//元素的回位力 
                toOriginal.Normalize();
                if ((initPos - childPos).Length < (toOriginal * pospush * seconds).Length)
                { //该时间产生的位移将要超过目标点，此时按距离目标点的距离来缩小加速度，距离越近，获得的速度越小。
                    forces += (toOriginal * pospush) * ((targetPos - childPos).Length / (toOriginal * pospush * seconds).Length);
                }
                else
                {
                    forces += (toOriginal * pospush);
                }


            }
            if (TargetForce != 0d)
            {
                if (!double.IsNaN(targetPos.X) && !double.IsNaN(targetPos.Y))
                {//计算目标力 
                    var toTarget = targetPos - childPos;

                    var pospush = TargetForce;//元素的目标力 
                    toTarget.Normalize();
                    if ((targetPos - childPos).Length < (toTarget * pospush * seconds).Length)
                    { //该时间产生的位移将要超过目标点，此时按距离目标点的距离来缩小加速度，距离越近，获得的速度越小。
                        forces += (toTarget * pospush) * ((targetPos - childPos).Length / (toTarget * pospush * seconds).Length);
                    }
                    else
                    {
                        forces += (toTarget * pospush);
                    }


                }
            }
            if (forces.Length > 1d)
            {//忽略过于微小的力
                curVelocity += forces;
            }
            if (this.DragForce > 0 && (curVelocity * this.DragForce).Length > curVelocity.Length)
            {//阻力大于当前速度，停下来而不是使运动转向。
                curVelocity = new Vector(0, 0);
            }
            else
            {
                curVelocity -= curVelocity * (this.DragForce);//计算阻力
            }
            return curVelocity;
        }

        /// <summary>
        /// 在所有子元素都更新完成时触发
        /// </summary>
        public event RoutedEventHandler ChildrenUpdated
        {
            add { AddHandler(ChildrenUpdatedEvent, value); }
            remove { RemoveHandler(ChildrenUpdatedEvent, value); }
        }

        public static readonly RoutedEvent ChildrenUpdatedEvent = EventManager.RegisterRoutedEvent(
        "ChildrenUpdated", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MechanicsCanvas));

        protected virtual void OnChildrenUpdated()
        {
            RoutedEventArgs args = new RoutedEventArgs();
            args.RoutedEvent = MechanicsCanvas.ChildrenUpdatedEvent;
            this.RaiseEvent(args);
        }



        protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
        {
            base.OnVisualChildrenChanged(visualAdded, visualRemoved);
            if (visualAdded is UIElement uiele)
            {
                if (double.IsNaN(Canvas.GetLeft(uiele)))
                {
                    Canvas.SetLeft(uiele, 0d);
                }
                if (double.IsNaN(Canvas.GetTop(uiele)))
                {
                    Canvas.SetTop(uiele, 0d);
                };
            }
            if (visualRemoved is UIElement ue && this._velocities.ContainsKey(ue))
            {//移除储存的速度
                this._velocities.Remove(ue);
            }
        }

        /// <summary>
        /// 直接设置元素的位置
        /// </summary>
        /// <param name="element"></param>
        /// <param name="p"></param>
        public void SetPosition(UIElement element, Point p)
        {
            Canvas.SetLeft(element, double.IsNaN(p.X) ? 0 : p.X);
            Canvas.SetTop(element, double.IsNaN(p.Y) ? 0 : p.Y);
        }
        /// <summary>
        /// 获取元素当前的位置
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static Point GetPosition(UIElement element)
        {
            var x = Canvas.GetLeft(element);
            var y = Canvas.GetTop(element);
            return new Point(double.IsNaN(x) ? 0 : x, double.IsNaN(y) ? 0 : y);
        }
        /// <summary>
        /// 两个矩形是否相交
        /// </summary>
        /// <param name="r1"></param>
        /// <param name="r2"></param>
        /// <returns></returns>
        private static bool AreRectsOverlapping(Rect r1, Rect r2)
        {
            if (r1.Bottom < r2.Top) return false;
            if (r1.Top > r2.Bottom) return false;

            if (r1.Right < r2.Left) return false;
            if (r1.Left > r2.Right) return false;

            return true;
        }
        /// <summary>
        /// 求射线与矩形的交点
        /// </summary>
        /// <param name="r">矩形</param>
        /// <param name="raystart">射线起点</param>
        /// <param name="raydir">射线方向</param>
        /// <returns></returns>
        protected static Point IntersectInsideRect(Rect r, Point raystart, Vector raydir)
        {
            var xtop = raystart.X + raydir.X * (r.Top - raystart.Y) / raydir.Y;
            var xbottom = raystart.X + raydir.X * (r.Bottom - raystart.Y) / raydir.Y;
            var yleft = raystart.Y + raydir.Y * (r.Left - raystart.X) / raydir.X;
            var yright = raystart.Y + raydir.Y * (r.Right - raystart.X) / raydir.X;
            var top = new Point(xtop, r.Top);
            var bottom = new Point(xbottom, r.Bottom);
            var left = new Point(r.Left, yleft);
            var right = new Point(r.Right, yright);
            var tv = raystart - top;
            var bv = raystart - bottom;
            var lv = raystart - left;
            var rv = raystart - right;
            //classify ray direction
            if (raydir.Y < 0)
            {
                if (raydir.X < 0) //top left
                {
                    if (tv.LengthSquared < lv.LengthSquared)
                        return top;
                    return left;
                }
                if (tv.LengthSquared < rv.LengthSquared)
                    return top;
                return right;
            }
            if (raydir.X < 0) //bottom left
            {
                if (bv.LengthSquared < lv.LengthSquared)
                    return bottom;
                return left;
            }
            if (bv.LengthSquared < rv.LengthSquared)
                return bottom;
            return right;
        }

        /// <summary>
        /// 计算两个矩形中心的向量
        /// </summary>
        /// <param name="r1"></param>
        /// <param name="r2"></param>
        /// <returns></returns>
        protected static Vector VectorBetweenRects(Rect r1, Rect r2)
        {
            var r1Center = GetCenter(r1);
            var r2Center = GetCenter(r2);
            var between = (r1Center - r2Center);
            between.Normalize();
            var edge1 = IntersectInsideRect(r1, r1Center, -between);
            var edge2 = IntersectInsideRect(r2, r2Center, between);
            return edge1 - edge2;
        }
        /// <summary>
        /// 获取矩形的中心
        /// </summary>
        /// <param name="r"></param>
        /// <returns></returns>
        protected static Point GetCenter(Rect r) => new Point((r.Left + r.Right) / 2.0, (r.Top + r.Bottom) / 2.0);

        #endregion

    }
}
